﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks.Dataflow;

namespace Corecalc
{
    public abstract class Parent
    {
        public Parent MyParent { get; set; }
    }

    public class GRGraph : Parent
    {
        public GRNode Root { get; set; }
        public List<GRNode> Nodes { get; set; }
        public List<CellAreaNode> Cans { get; set; }

        public GRGraph() {
            Cans = new List<CellAreaNode>();
        }

        public double Ft { get; set; }                  //Sum of Ft for all nodes in the graph
        public double CP { get; set; }                  //Critical Path value for entire graph
        public double O { get; set; }                   //TSched + T_in() + T_out() for the entire graph        TBD!!
        public double F { get { return 1; } }           //Number of times this graph is executed

        public List<GRNode> GetInputEdges() {
            List<GRNode> inputEdges = new List<GRNode>();
            foreach (GRNode node in Nodes) {
                foreach (GRNode input in node.InputEdges) {
                    if (input.MyParent != node.MyParent) {

                        if (input.Host == node.Host) {
                            throw new Exception("");
                        }
                        
                        
                        inputEdges.Add(input);
                    }
                }
            }
            return inputEdges;
        }
    }

    public abstract class GRNode : Parent
    {
        public List<GRNode> InputEdges { get; set; }
        public List<GRNode> OutputEdges { get; set; }
        public FullCellAddr Host { get; set; }
        public bool DFSVisited { get; set; }

        public double CP { get; set; }
        public virtual double Ft { get; set; }
        public virtual double T_in { get; set; }
        public virtual double T_out { get; set; }
        public Task Task { get; set; }
        public Task TempTask { get; set; }

        public GRNode() {
            InputEdges = new List<GRNode>();
            OutputEdges = new List<GRNode>();
        }

        public void DepthFirstSearch(Action<GRNode> f) {
            List<GRNode> nodes = new List<GRNode>();

            Stack<GRNode> stack = new Stack<GRNode>();
            stack.Push(this);
            while (stack.Count > 0) {
                GRNode node = stack.Pop();
                if (!node.DFSVisited) {
                    node.DFSVisited = true;
                    f(node);
                    nodes.Add(node);

                    foreach (GRNode input in node.InputEdges)
                        stack.Push(input);
                }
            }

            foreach (GRNode node in nodes)
                node.DFSVisited = false;
        }

        public override string ToString() {
            return string.Format("{0}, Fca: {1}", GetType(), Host);
        }
    }

    abstract public class SimpleNode : GRNode
    {

    }

    public abstract class CompoundNode : GRNode
    {
        public List<GRGraph> Graphs { get; set; }

        public CompoundNode() {
            Graphs = new List<GRGraph>();
        }

        public abstract List<GRNode> ContainedNodes();
    }

    public abstract class ParallelNode : GRNode
    {
        public GRGraph Graph { get; set; }

        public double F { get; set; }
        public int Size { get; set; }
    }

    public class BinopNode : SimpleNode
    {
        public string Operator { get; set; }
        public GRNode Left { get; set; }
        public GRNode Right { get; set; }

        public BinopNode(string op, GRNode[] args) {
            Operator = op;
            Left = args[0];
            Right = args[1];

            InputEdges.Add(Left);
            Left.OutputEdges.Add(this);

            InputEdges.Add(Right);
            Right.OutputEdges.Add(this);

            Ft = MPModel.GetExecutionTime(op);
            T_in = Left.T_out + Right.T_out;
            T_out = MPModel.GetCommunicationCost("double");     //Only holds for +, -, < etc..
        }
    }

    public class BuiltInFunctionCallNode : SimpleNode
    {
        public string Name { get; set; }
        public bool Par { get; set; }

        public BuiltInFunctionCallNode(string name, GRNode[] args) {
            Name = name;
            
            foreach (GRNode arg in args) {
                InputEdges.Add(arg);
                arg.OutputEdges.Add(this);
            }

            //ExecutionTimeCost is set by InitCalls

            Debug.Assert(name.Equals("SUM") || name.Equals("COS") || name.Equals("SIN") || name.Equals("SQRT"));
            T_in = args.Sum(arg => arg.T_out);
            T_out = MPModel.GetCommunicationCost("double");   //Should depend on the concrete function that is called
        }
    }

    public class IfNode : CompoundNode
    {
        public GRGraph True { get; set; }
        public GRGraph False { get; set; }

        public IfNode(GRNode cond, GRGraph @true, GRGraph @false) {
            InputEdges.Add(cond);
            cond.OutputEdges.Add(this);

            True = @true;
            False = @false;

            Graphs.Add(True);
            Graphs.Add(False);

            //Ft for IF-/Compound Nodes are set in AssignCosts
            T_in = cond.T_out + Math.Max(@true.GetInputEdges().Sum(e => e.T_out), @false.GetInputEdges().Sum(e => e.T_out));
            T_out = Math.Max(True.Root.T_out, False.Root.T_out);
        }

        public override List<GRNode> ContainedNodes() {
            List<GRNode> nodes = new List<GRNode>();
            nodes.AddRange(True.Nodes.Where(n => n is CompoundNode).Select(n => n as CompoundNode).SelectMany(n => n.ContainedNodes()));
            nodes.AddRange(False.Nodes.Where(n => n is CompoundNode).Select(n => n as CompoundNode).SelectMany(n => n.ContainedNodes()));
            nodes.AddRange(True.Nodes);
            nodes.AddRange(False.Nodes);
            return nodes;
        }
    }

    public class ConstNode : SimpleNode
    {
        public Const Const { get; set; }

        public ConstNode(Const @const, FullCellAddr fca) {
            Const = @const;

            Ft = MPModel.GetExecutionTime("const");
            T_in = 0.0;
            T_out = Const.CommunicationCost(fca);
        }
    }

    public class CellRefNode : SimpleNode
    {
        public Cell Cell { get; set; }      //Non-null if not part of the support graph
        
        public CellRefNode(GRNode node) {
            InputEdges.Add(node);
            node.OutputEdges.Add(this);

            Ft = MPModel.GetExecutionTime("cellref");
            T_out = T_in = node.T_out;
        }

        //This constructor does not set the "Host" field, fca only required for communication cost lookup
        public CellRefNode(Cell cell, FullCellAddr fca) {
            Cell = cell;

            Ft = MPModel.GetExecutionTime("cellref");
            T_out = T_in = MPModel.GetCommunicationCost(cell, fca);
        }
    }

    public class CellAreaNode : SimpleNode
    {
        public CellAreaNode(List<GRNode> nodes) {
            foreach (var node in nodes) {
                InputEdges.Add(node);
                node.OutputEdges.Add(this);
            }

            Ft = MPModel.GetExecutionTime("cellref") * InputEdges.Count;
            T_out = T_in = nodes.Sum(n => n.T_out);
        }

        public void RemoveFromGraph() {
            foreach (GRNode outputEdge in OutputEdges) {
                outputEdge.InputEdges.Remove(this);
                foreach (GRNode inputEdge in InputEdges) {
                    outputEdge.InputEdges.Add(inputEdge);
                }
            }

            foreach (GRNode inputEdge in InputEdges) {
                inputEdge.OutputEdges.Remove(this);
                foreach (GRNode outputEdge in OutputEdges) {
                    inputEdge.OutputEdges.Add(outputEdge);
                }
            }
        }
    }

    public class Utils
    {
        public static List<A> TopSort<A>(IEnumerable<A> items, Func<A, IEnumerable<A>> outgoing) {
            List<A> sortedList = new List<A>();
            List<A> marked = new List<A>();
            List<A> tempMarked = new List<A>();

            Action<A> visit = null;
            visit = n => {
                if (tempMarked.Contains(n)) throw new Exception("Uh oh..!!");
                if (!marked.Contains(n)) {
                    tempMarked.Add(n);

                    var o = outgoing(n);
                    foreach (A m in o)
                        visit(m);

                    marked.Add(n);
                    tempMarked.Remove(n);
                    sortedList.Add(n);      //Should've been AddFirst, we reverse the list instead
                }
            };

            Queue<A> unmarked = new Queue<A>(items);
            while (unmarked.Count > 0) {
                A n = unmarked.Dequeue();
                visit(n);
            }

            sortedList.Reverse();
            return sortedList;
        }
    }

    class PartitioningResult
    {
        public int TaskCount { get; set; }
        public Dictionary<int, int> NodesInTasks { get; set; }
        public double BuildSecs { get; set; }
        public double PartitioningSecs { get; set; }
        public double GraphCPBefore { get; set; }
        public double GraphCPAfter { get; set; }
    }
}
